Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit a44c1f368a28e7a73a4d083ec622e52865d43923


Parents : 4342677
Author : Mark Qvist <mark@unsigned.io>
Date : 2025-10-31T17:02:34+01:00

Validate peering key on incoming sync offer

Changes

3 files changed, 42 insertions(+), 18 deletions(-)


Diff

diff --git a/LXMF/LXMPeer.py b/LXMF/LXMPeer.py
index 0fe1e74..7d851f2 100644
--- a/LXMF/LXMPeer.py
+++ b/LXMF/LXMPeer.py
@@ -22,7 +22,9 @@ class LXMPeer:
ERROR_NO_IDENTITY = 0xf0
ERROR_NO_ACCESS = 0xf1
- ERROR_THROTTLED = 0xf2
+ ERROR_INVALID_KEY = 0xf3
+ ERROR_INVALID_DATA = 0xf4
+ ERROR_THROTTLED = 0xf5
ERROR_TIMEOUT = 0xfe
STRATEGY_LAZY = 0x01
@@ -369,9 +371,11 @@ class LXMPeer:
cumulative_size += lxm_transfer_size
unhandled_ids.append(transient_id)
+ offer = [self.peering_key[0], unhandled_ids]
+
RNS.log(f"Offering {len(unhandled_ids)} messages to peer {RNS.prettyhexrep(self.destination.hash)} ({RNS.prettysize(len(msgpack.packb(unhandled_ids)))})", RNS.LOG_VERBOSE)
self.last_offer = unhandled_ids
- self.link.request(LXMPeer.OFFER_REQUEST_PATH, unhandled_ids, response_callback=self.offer_response, failed_callback=self.request_failed)
+ self.link.request(LXMPeer.OFFER_REQUEST_PATH, offer, response_callback=self.offer_response, failed_callback=self.request_failed)
self.state = LXMPeer.REQUEST_SENT
else:

diff --git a/LXMF/LXMRouter.py b/LXMF/LXMRouter.py
index ce6b685..bac498b 100644
--- a/LXMF/LXMRouter.py
+++ b/LXMF/LXMRouter.py
@@ -45,8 +45,8 @@ class LXMRouter:
ROTATION_HEADROOM_PCT = 10
ROTATION_AR_MAX = 0.5
- PEERING_COST = 10
- MAX_PEERING_COST = 12
+ PEERING_COST = 18
+ MAX_PEERING_COST = 24
PROPAGATION_COST_MIN = 13
PROPAGATION_COST_FLEX = 3
PROPAGATION_COST = 16
@@ -2047,21 +2047,30 @@ class LXMRouter:
return LXMPeer.ERROR_NO_ACCESS
try:
- transient_ids = data
- wanted_ids = []
+ if type(data) != list and len(data) < 2: return LXMPeer.ERROR_INVALID_DATA
- for transient_id in transient_ids:
- if not transient_id in self.propagation_entries:
- wanted_ids.append(transient_id)
+ peering_id = self.identity.hash+remote_identity
+ target_cost = self.peering_cost
+ peering_key = data[0]
+ transient_ids = data[1]
+ wanted_ids = []
- if len(wanted_ids) == 0:
- return False
+ ts = time.time()
+ peering_key_valid = LXStamper.validate_peering_key(peering_id, peering_key, target_cost)
+ td = time.time() - ts
- elif len(wanted_ids) == len(transient_ids):
- return True
+ if not peering_key_valid:
+ RNS.log(f"Invalid peering key for incoming sync offer", RNS.LOG_DEBUG)
+ return LXMPeer.ERROR_INVALID_KEY
else:
- return wanted_ids
+ RNS.log(f"Peering key validated for incoming offer in {RNS.prettytime(td)}", RNS.LOG_DEBUG)
+ for transient_id in transient_ids:
+ if not transient_id in self.propagation_entries: wanted_ids.append(transient_id)
+
+ if len(wanted_ids) == 0: return False
+ elif len(wanted_ids) == len(transient_ids): return True
+ else: return wanted_ids
except Exception as e:
RNS.log("Error occurred while generating response for sync request, the contained exception was: "+str(e), RNS.LOG_DEBUG)
@@ -2069,9 +2078,6 @@ class LXMRouter:
def propagation_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
- # TODO: The peer this was received from should
- # have the transient id added to its list of
- # already handled messages.
try:
data = msgpack.unpackb(resource.data.read())

diff --git a/LXMF/LXStamper.py b/LXMF/LXStamper.py
index 4d2e38c..2a74295 100644
--- a/LXMF/LXStamper.py
+++ b/LXMF/LXStamper.py
@@ -8,8 +8,8 @@ import itertools
import multiprocessing
WORKBLOCK_EXPAND_ROUNDS = 3000
-WORKBLOCK_EXPAND_ROUNDS_PEERING = 20000
WORKBLOCK_EXPAND_ROUNDS_PN = 1000
+WORKBLOCK_EXPAND_ROUNDS_PEERING = 25
STAMP_SIZE = RNS.Identity.HASHLENGTH
PN_VALIDATION_POOL_MIN_SIZE = 256
@@ -45,6 +45,11 @@ def stamp_valid(stamp, target_cost, workblock):
if int.from_bytes(result, byteorder="big") > target: return False
else: return True
+def validate_peering_key(peering_id, peering_key, target_cost):
+ workblock = stamp_workblock(peering_id, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PEERING)
+ if not stamp_valid(peering_key, target_cost, workblock): return False
+ else: return True
+
def validate_pn_stamp(transient_data, target_cost):
from .LXMessage import LXMessage
if len(transient_data) <= LXMessage.LXMF_OVERHEAD+STAMP_SIZE: return False, None, None
@@ -348,6 +353,13 @@ def job_android(stamp_cost, workblock, message_id):
return stamp, total_rounds
+# def stamp_value_linear(workblock, stamp):
+# value = 0
+# bits = 256
+# material = RNS.Identity.full_hash(workblock+stamp)
+# s = int.from_bytes(material, byteorder="big")
+# return s.bit_count()
+
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
@@ -365,10 +377,12 @@ if __name__ == "__main__":
message_id = os.urandom(32)
generate_stamp(message_id, cost)
+ RNS.log("", RNS.LOG_DEBUG)
RNS.log("Testing propagation stamp generation", RNS.LOG_DEBUG)
message_id = os.urandom(32)
generate_stamp(message_id, cost, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PN)
+ RNS.log("", RNS.LOG_DEBUG)
RNS.log("Testing peering key generation", RNS.LOG_DEBUG)
message_id = os.urandom(32)
generate_stamp(message_id, cost, expand_rounds=WORKBLOCK_EXPAND_ROUNDS_PEERING)
\ No newline at end of file


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────